package org.dromara.web.modbus.utils;

import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.system.domain.VariableAssociation;
import org.dromara.system.utils.SystemUtils;
import org.dromara.web.listener.AutoCloseableModbusMaster;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * 动环数据工具类
 */
@Slf4j
@RequiredArgsConstructor
public class RotatingUtils {
    private static final ReentrantLock lock = new ReentrantLock();
    private static final String HOST = "192.168.1.48";
    private static final int PORT = 504;
    private static final ModbusFactory MODBUS_FACTORY = new ModbusFactory();
    private static ModbusMaster modbusMaster;

    static {
        try {
            modbusMaster = initMaster();
        } catch (ModbusInitException e) {
            log.error("ModbusMaster 初始化失败", e);
            throw new RuntimeException("ModbusMaster 初始化失败", e);
        }
    }

    private static ModbusMaster initMaster() throws ModbusInitException {
        IpParameters params = new IpParameters();
        params.setHost(HOST);
        params.setPort(PORT);
        ModbusMaster master = MODBUS_FACTORY.createTcpMaster(params, true);
        master.setTimeout(3000);
        master.init();
        return master;
    }

    public static ModbusMaster getMaster() {
        return modbusMaster;
    }

    public static Map<Integer, Number> batchReadConcurrently(List<VariableAssociation> variableAssociations, int batchSize, int threadCount)
        throws InterruptedException, ExecutionException {
        if (variableAssociations == null || variableAssociations.isEmpty()) {
            return Collections.emptyMap();
        }

        ForkJoinPool customThreadPool = new ForkJoinPool(threadCount);
        List<CompletableFuture<Map<Integer, Number>>> futures = new ArrayList<>();

        for (int i = 0; i < variableAssociations.size(); i += batchSize) {
            int start = i;
            int end = Math.min(i + batchSize, variableAssociations.size());
            List<VariableAssociation> batch = variableAssociations.subList(start, end);
            futures.add(CompletableFuture.supplyAsync(() -> {
                try {
                    return batchRead(batch);
                } catch (Exception e) {
                    log.error("批次读取失败", e);
                    throw new RuntimeException("批次读取失败", e);
                }
            }, customThreadPool));
        }

        Map<Integer, Number> resultMap = futures.stream()
            .map(CompletableFuture::join)
            .flatMap(map -> map.entrySet().stream())
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (existing, replacement) -> existing
            ));

        customThreadPool.shutdown();
        customThreadPool.awaitTermination(1, TimeUnit.MINUTES);

        return resultMap;
    }

    private static Map<Integer, Number> batchRead(List<VariableAssociation> batch) throws Exception {
        try (AutoCloseableModbusMaster autoMaster = new AutoCloseableModbusMaster(HOST, PORT)) {
            Map<Integer, Number> results = new HashMap<>();
            for (VariableAssociation variable : batch) {
                int offset = Integer.parseInt(variable.getCode());
                Number value;
                if (variable.getVariableType() == 1) {
                    value = readModbusRegisterBoolean(offset, variable.getSlaveId());
                    log.info("类型为01,变量名：{},传感器地址为{},数值为：{}", variable.getName(), variable.getCode(), value);
                } else if (variable.getVariableType() == 2) {
                    value = readModbusRegisterFloat(offset, variable.getSlaveId());
                    log.info("类型为03,变量名：{},传感器地址为{},数值为：{}", variable.getName(), variable.getCode(), value);
                } else {
                    throw new IllegalArgumentException("Unsupported variable type: " + variable.getVariableType());
                }
                results.put(offset, value);
            }
            return results;
        } catch (Exception e) {
            log.error("批次读取失败: {}", e.getMessage());
            throw e;
        }
    }

    public static Number batchReadOne(VariableAssociation variable) throws Exception {
        try (AutoCloseableModbusMaster autoMaster = new AutoCloseableModbusMaster(HOST, PORT)) {
            int offset = Integer.parseInt(variable.getCode());
            Number value;
            if (variable.getVariableType() == 1) {
                value = readModbusRegisterBoolean(offset, variable.getSlaveId());
                log.info("类型为01,变量名：{},传感器地址为{},数值为：{}", variable.getName(), variable.getCode(), value);
            } else if (variable.getVariableType() == 2) {
                value = readModbusRegisterFloat(offset, variable.getSlaveId());
                log.info("类型为03,变量名：{},传感器地址为{},数值为：{}", variable.getName(), variable.getCode(), value);
            } else {
                value = readInputRegister(offset, variable.getSlaveId());
            }
            SystemUtils.setYtServerStatus(1);
            return value;
        } catch (Exception e) {
            SystemUtils.setYtServerStatus(0);
            log.error("读取失败: {}", e.getMessage());
            throw e;
        }
    }

    private static Number readModbusRegisterBoolean(int offset, int slaveId) throws ModbusTransportException, ErrorResponseException {
        ModbusMaster master = getMaster();
        BaseLocator<Boolean> locator = BaseLocator.coilStatus(slaveId, offset);
        return master.getValue(locator) ? 1.0 : 0.0;
    }

    private static Number readModbusRegisterFloat(int offset, int slaveId) throws ModbusTransportException, ErrorResponseException {
        lock.lock();
        try {
            BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, DataType.FOUR_BYTE_FLOAT);
            return getMaster().getValue(locator);
        } finally {
            lock.unlock();
        }
    }

    private static Number readInputRegister(int offset, int slaveId) throws ModbusTransportException, ErrorResponseException {
        lock.lock();
        try {
            BaseLocator<Number> locator = BaseLocator.inputRegister(slaveId, offset, DataType.FOUR_BYTE_FLOAT);
            return getMaster().getValue(locator);
        } finally {
            lock.unlock();
        }
    }
}
